﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace JasLib.Threading
{
    /// <summary>
    /// http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266920.aspx
    /// </summary>
    public class AsyncManualResetEvent : IDisposable
    {
        /// <summary>
        /// Doesn't matter what type the generic TaskCompletionSource uses for a result.
        /// As soon as this field is set to null, the event object is officially disposed.
        /// </summary>
        private volatile TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>();

        public AsyncManualResetEvent(bool bCreateSignalled)
        {
            if (bCreateSignalled)
                _tcs.TrySetResult(null);
            return;
        }

        public void Dispose()
        {
            var tcs = Interlocked.Exchange(ref _tcs, null);
            if (tcs != null)
                tcs.TrySetException(CreateDisposedException());
            return;
        }

        private Exception CreateDisposedException()
        {
            return new ObjectDisposedException("AsyncManualResetEvent", "This AsyncManualResetEvent is no longer in use.");
        }

        public Task WaitAsync()
        {
            var tcs = _tcs;
            if (tcs == null)
                throw CreateDisposedException();
            return tcs.Task;
        }

        /// <summary>
        /// Avoid using this for anything other than diagnostic purposes.
        /// </summary>
        public bool IsSet
        {
            get
            {
                var tcs = _tcs;
                if (tcs == null)
                    throw CreateDisposedException();
                return tcs.Task.IsCompleted;
            }
        }

        public void Set()
        {
            var tcs = _tcs;
            if (tcs == null)
                throw CreateDisposedException();
            tcs.TrySetResult(null);
            return;
        }

        protected void Reset(bool bSetFirst)
        {
            while (true)
            {
                var tcs = _tcs;
                if (tcs == null)
                    throw CreateDisposedException();

                if (bSetFirst)
                    tcs.TrySetResult(null);

                /// The task is still clean. Don't discard it.
                else if (!tcs.Task.IsCompleted)
                    return;

                /// If the value hasn't changed since the above checks, insert a new TCS.
                /// Otherwise this TCS is wasted and goes to the GC, and we loop to try again.
                /// So, if the TCS was set to null just before this line, the assignment safely won't happen.
                if (Interlocked.CompareExchange(ref _tcs, new TaskCompletionSource<object>(), tcs) == tcs)
                {
                    return;
                }
            }
        }

        public void Reset()
        {
            Reset(false);
            return;
        }

        public void Pulse()
        {
            /// This is not as simple as Set() and Reset() because the operation must be atomic.
            Reset(true);
            return;
        }
    }
}